package ua.yandex.shad.tempseries;

import java.lang.Math.*;
import Jama.*;
import java.util.Vector;



public class TemperatureSeriesAnalysis {
    
    private final double[] temperatureSeries;

    public TemperatureSeriesAnalysis(double[] temperatureSeries) throws IllegalTemperatureValueException {
        int index=0;
        for (double d : temperatureSeries){
            if(d<-273.0)
                throw new IllegalTemperatureValueException(index);
            index++;
        }
        this.temperatureSeries = temperatureSeries;
    }
    
    public double calcAverageTemp(){
        if(temperatureSeries.length==0)
            return 0;
        double sum = 0;
        for (double d : temperatureSeries)
            sum += d;
        return sum/temperatureSeries.length;
    }    
    
    public double calcTempDeviation(){
        if(temperatureSeries.length==0)
            return 0;
        double deviation=0;
        double expected_val=0;
        for (double d : temperatureSeries){
            deviation += Math.pow(d,2.0);
            expected_val += d;
        }
        deviation/=temperatureSeries.length;
        expected_val/=temperatureSeries.length;
        return deviation-Math.pow(expected_val,2.0);
    }
    
    public double findTempClosestToZero(){
        int length=temperatureSeries.length;
        if(length==0)
            return 0;
        double closest=temperatureSeries[0];
        for (int i=1; i<length; i++)
            if(Math.abs(closest)<Math.abs(temperatureSeries[i]))
                continue;
            else if(Math.abs(closest)>Math.abs(temperatureSeries[i]))
                closest=temperatureSeries[i];
            else if(temperatureSeries[i]>closest)
                closest=temperatureSeries[i];
        return closest;
    }
    
    public double findTempClosestToValue(double tempValue){
        int length=temperatureSeries.length;
        if(length==0)
            return 0;
        double closest=temperatureSeries[0];
        for (int i=1; i<length; i++)
            if(Math.abs(closest - tempValue)<Math.abs(temperatureSeries[i] - tempValue))
                continue;
            else if(Math.abs(closest - tempValue)>Math.abs(temperatureSeries[i] - tempValue))
                closest=temperatureSeries[i];
            else if(temperatureSeries[i]> closest)
                closest=temperatureSeries[i];
        return closest;
    }
    
    public double[] findTempsLessThan(double tempValue){
        int length=temperatureSeries.length;
        int[] indexes = new int[length];
        int count = 0;
        double[] out;
        for(int i = 0 ; i < length ; i++)
            if ( temperatureSeries[i] < tempValue ){
                indexes[count] = i;
                count++;
            }
        out = new double[count];
        for(int i = 0 ; i < count ; i++)
            out[i] = temperatureSeries[indexes[i]];
        return out;
    }
    
    public double[] findTempsGreaterThan(double tempValue){
        int length=temperatureSeries.length;
        int[] indexes = new int[length];
        int count = 0;
        double[] out;
        for(int i = 0 ; i < length ; i++)
            if ( temperatureSeries[i] > tempValue ){
                indexes[count] = i;
                count++;
            }
        out = new double[count];
        for(int i = 0 ; i < count ; i++)
            out[i] = temperatureSeries[indexes[i]];
        return out;
    }
    
    public double[] forecatsTemp() throws NotEnoughValuesToForecastException {

        int n = temperatureSeries.length;
        if( n < 10 )
            throw(new NotEnoughValuesToForecastException(n));

        
        double[] working_array = new double[n+3];
        for (int i =0 ; i < n ; i++)
            working_array[i] = temperatureSeries[i];

        int[] chosen_indexes;
        int r;
        int[] columns_array;
		int l;
		int k;

        SingularValueDecomposition Csvd;

        Matrix Z;
        Matrix C;

        Matrix V;
        Matrix S;

        Matrix Vstar;
        Matrix Vstar_transposed;
        Matrix v;
        Matrix Q;

        for(int z = 0; z < 3 ; z++){
			
			l = (n + z) / 2;
            k = (n + z) - l + 1;

            double[][] y = new double[k][l];

            for(int i = 0; i < k; i++)
                for(int j = 0; j < l ; j++)
                    y[i][j] = working_array[ i + j ];

            Z = new Matrix(y);
            Z = Z.transpose();

            C = Z.times(Z.transpose()).times(1.0/k);

            Csvd = new SingularValueDecomposition(C);
            V = Csvd.getV();
            S = Csvd.getS();

            chosen_indexes = new int[l];
            r = 0;
            for(int i = 0 ; i < l ; i++)
                if ( Math.abs(S.get(i,i)) > 0.00001 ){
                    chosen_indexes[r] = i;
                    r++;
                }

            columns_array = new int[r];
            for(int i = 0; i < r; i++)
                columns_array[i] = chosen_indexes[i];

            Vstar = V.getMatrix(0, V.getRowDimension() - 2,columns_array);

            v = V.getMatrix(V.getRowDimension() - 1, V.getRowDimension() - 1, columns_array);

            Q = new Matrix(l - 1,1);
            for(int i = 0 ; i < l - 1; i ++)
                Q.set(i,0,working_array[n + z - l + 1 + i]);

            Vstar_transposed = Vstar.transpose();

            v = v.times(Vstar_transposed.times(Vstar).inverse().times(Vstar_transposed)).times(Q);
            working_array[n+z] = v.get(0,0);
        }
        double[] ans = new double[3];
        ans[0] = working_array[n];
        ans[1] = working_array[n+1];
        ans[2] = working_array[n+2];
        return ans;
    }
}
